/*
 * Copyright (C) 2009 M. Hautle.
 * 
 * This file is part of TrackedBean.
 * 
 * TrackedBean is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option) any later version.
 * 
 * TrackedBean is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License along with TrackedBean. If not, see <http://www.gnu.org/licenses/>.
 */
package ch.trackedbean.copier;

import ch.msoftch.internal.*;
import ch.trackedbean.copier.annotations.*;
import ch.trackedbean.copier.internal.*;
import ch.trackedbean.internal.*;
import ch.trackedbean.loaders.lazy.*;

/**
 * Manager holding {@link IBeanMapper}s.<br>
 * 
 * To use a own {@link IBeanMapperFactory} simply create a service provider file for it:<br>
 * Thus is a <code>META-INF/services/ch.trackedbean.copier.IBeanMapperFactory</code> file containing the fully qualified name of your implementation.<br>
 * 
 * @author M. Hautle
 */
public final class BeanMapperManager {
    /** Empty mapper. */
    private static final IBeanMapper NOP_MAPPER = new BeanMapper(Object.class, Object.class);

    /** The factory to use. */
    private static IBeanMapperFactory factory;

    /** Mapper cache. */
    private static final ICache<Class, IBeanMapper> MAPPER_CACHE = CacheFactory.createCache(Class.class, IBeanMapper.class);

    static {
        factory = ServiceHelper.getService(IBeanMapperFactory.class, DefaultMapperFactory.class);
        MAPPER_CACHE.put(Object.class, NOP_MAPPER);
    }

    /**
     * Returns the mapper for the specified class
     * 
     * @param c The mapper target class
     * @return The corresponding mapper
     * @throws MapperInitialisationException If something went wrong
     */
    public static IBeanMapper getMapper(Class<?> c) throws MapperInitialisationException {
        IBeanMapper val = MAPPER_CACHE.get(c);
        if (val == null) {
            val = factory.createMapper(c);
            if (!val.doesSomething())
                val = NOP_MAPPER;
            MAPPER_CACHE.put(c, val);
        }
        return val;
    }

    /**
     * Copies the value from the src object into the dst object.<br>
     * All properties except those annotated with {@link LoadLazy} were copied.
     * 
     * @param <T> The destination bean type
     * 
     * @param dst The object holding the mapping
     * @param src The other object
     * @return The filled destination (dst) object
     * @throws TechnicalException If something goes wrong
     */
    public static <T> T copySrc2Dst(Object src, T dst) {
        return copySrc2Dst(src, dst, DefaultMappingTypes.NO_LAZY);
    }

    /**
     * Copies the value from the dst object into the src object.<br>
     * All properties except those annotated with {@link LoadLazy} were copied.
     * 
     * @param <T> The source bean type
     * 
     * @param dst The object holding the mapping
     * @param src The other object
     * @return The filled source (src) object
     * @throws TechnicalException If something goes wrong
     */
    public static <T> T copyDst2Src(Object dst, T src) {
        return copyDst2Src(dst, src, DefaultMappingTypes.NO_LAZY);
    }

    /**
     * Copies all values from the src object into the dst object.<br>
     * 
     * @param <T> The destination bean type
     * 
     * @param dst The object holding the mapping
     * @param src The other object
     * @return The filled destination (dst) object
     * @throws TechnicalException If something goes wrong
     */
    public static <T> T copyCompleteSrc2Dst(Object src, T dst) {
        return copySrc2Dst(src, dst, DefaultMappingTypes.ALL);
    }

    /**
     * Copies all values from the dst object into the src object.<br>
     * 
     * @param <T> The source bean type
     * 
     * @param dst The object holding the mapping
     * @param src The other object
     * @return The filled source (src) object
     * @throws TechnicalException If something goes wrong
     */
    public static <T> T copyCompleteDst2Src(Object dst, T src) {
        return copyDst2Src(dst, src, DefaultMappingTypes.ALL);
    }

    /**
     * Copies the value from the src object into the dst object.<br>
     * 
     * @param <T> The destination bean type
     * 
     * @param dst The object holding the mapping
     * @param src The other object
     * @param filter The filter to use
     * @return The filled destination (dst) object
     * @throws TechnicalException If something goes wrong
     */
    public static <T> T copySrc2Dst(Object src, T dst, IMappingFilter filter) {
        return copySrc2Dst(src, dst, new DefaultMappingContext(filter));
    }

    /**
     * Copies the value from the dst object into the src object.<br>
     * 
     * @param <T> The source bean type
     * 
     * @param dst The object holding the mapping
     * @param src The other object
     * @param filter The filter to use
     * @return The filled source (src) object
     * @throws TechnicalException If something goes wrong
     */
    public static <T> T copyDst2Src(Object dst, T src, IMappingFilter filter) {
        return copyDst2Src(dst, src, new DefaultMappingContext(filter));
    }

    /**
     * Copies the value from the src object into the dst object.<br>
     * 
     * @param <T> The destination bean type
     * 
     * @param dst The object holding the mapping
     * @param src The other object
     * @param context The mapping context to use
     * @return The filled destination (dst) object
     * @throws TechnicalException If something goes wrong
     */
    public static <T> T copySrc2Dst(Object src, T dst, IMappingContext context) {
        return getMapper(dst.getClass()).copySrc2Dst(src, dst, context);
    }

    /**
     * Copies the value from the dst object into the src object.<br>
     * 
     * @param <T> The source bean type
     * 
     * @param dst The object holding the mapping
     * @param src The other object
     * @param context The mapping context to use
     * @return The filled source (src) object
     * @throws TechnicalException If something goes wrong
     */
    public static <T> T copyDst2Src(Object dst, T src, IMappingContext context) {
        return getMapper(dst.getClass()).copyDst2Src(dst, src, context);
    }

    /**
     * Default implementation of {@link IBeanMapperFactory}.
     * 
     * @author M. Hautle
     */
    public static class DefaultMapperFactory implements IBeanMapperFactory {
        /**
         * {@inheritDoc}
         */
        @Override
        public IBeanMapper createMapper(Class<?> type) throws MapperInitialisationException {
            return new DefaultBeanMapperConfigurator(type).create();
        }
    }
}
